package ldh.im.rpc.codec;

import ldh.im.rpc.buffer.RpcBuffer;
import ldh.im.rpc.buffer.RpcBufferUtil;
import ldh.im.rpc.config.RpcConfig;
import ldh.im.rpc.config.RpcMapperInfo;
import ldh.im.rpc.connection.RpcCallback;
import ldh.im.rpc.dto.RpcData;
import ldh.im.rpc.dto.RpcExtData;
import ldh.im.rpc.exception.ExceptionResp;
import ldh.im.rpc.util.RpcUtil;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;

import static ldh.im.rpc.buffer.RpcBufferUtil.utf8;

public class RpcDataCodec implements RpcCodec {

    private ObjectCodec objectCodec;
    private RpcConfig rpcConfig;

    public RpcDataCodec(RpcConfig rpcConfig, ObjectCodec objectCodec) {
        this.rpcConfig = rpcConfig;
        this.objectCodec = objectCodec;
    }

    @Override
    public void encode(RpcBuffer rpcBuffer, RpcData rpcData) {
        writeHeader(rpcBuffer, rpcData);
        writeExtData(rpcBuffer, rpcData);
        writeData(rpcBuffer, rpcData);
    }

    @Override
    public <T> RpcData<T> decode(RpcBuffer rpcBuffer) {
        RpcData rpcData = new RpcData();
        int length = rpcBuffer.readInt();
        readHeader(rpcBuffer, rpcData);
        readExtData(rpcBuffer, rpcData);
        readData(rpcBuffer, rpcData);
        return rpcData;
    }

    private void writeHeader(RpcBuffer rpcBuffer, RpcData rpcData) {
        RpcBufferUtil.writeString(rpcBuffer, rpcData.getPath(), utf8);
        rpcBuffer.writeInt(rpcData.getVersion());
        rpcBuffer.writeInt(rpcData.getStatus());
    }

    private void writeExtData(RpcBuffer rpcBuffer, RpcData rpcData) {
        RpcBufferUtil.writeMap(rpcBuffer, rpcData.getExtData().getExtDataMap(), (b, k)->RpcBufferUtil.writeString(b, k, utf8), (b, v)->RpcBufferUtil.writeString(b, v, utf8));
    }

    private void writeData(RpcBuffer rpcBuffer, RpcData rpcData) {
        objectCodec.encode(rpcBuffer, rpcData.getData());
    }

    private void readHeader(RpcBuffer rpcBuffer, RpcData rpcData) {
        String path = RpcBufferUtil.readString(rpcBuffer, utf8);
        Integer version = rpcBuffer.readInt();
        Integer status  = rpcBuffer.readInt();
        rpcData.setPath(path);
        rpcData.setStatus(status);
        rpcData.setVersion(version);
    }

    private void readExtData(RpcBuffer rpcBuffer, RpcData rpcData) {
        Map<String, String> extDataMap = RpcBufferUtil.readMap(rpcBuffer, b->RpcBufferUtil.readString(b, utf8), v->RpcBufferUtil.readString(v, utf8));
        rpcData.setExtData(new RpcExtData(extDataMap));
    }

    private void readData(RpcBuffer rpcBuffer, RpcData rpcData) {
        if (rpcData.getStatus() == -1) {  // 不成功
            ExceptionResp exceptionResp = (ExceptionResp) objectCodec.decode(rpcBuffer, ExceptionResp.class);
            rpcData.setData(exceptionResp);
            return;
        }

        if (rpcData.getStatus() == -1) {  // 不成功
            ExceptionResp exceptionResp = (ExceptionResp) objectCodec.decode(rpcBuffer, ExceptionResp.class);
            rpcData.setData(exceptionResp);
            return;
        }

        if (rpcData.getStatus() == 0) {  // 请求
            RpcMapperInfo rpcMapperInfo = rpcConfig.getRouter(rpcData.getPath());
            Class paraClass = RpcUtil.getRpcParam(rpcMapperInfo.getMethod());
            Object obj = decodeData(rpcBuffer, paraClass);
            rpcData.setData(obj);
        } else { // 请求返回值
            RpcMapperInfo rpcMapperInfo = rpcConfig.getServicePath(rpcData.getPath());
            if (isCallback(rpcMapperInfo)) {
                Method method = rpcMapperInfo.getMethod();
                Class returnType = RpcUtil.getMethodGenericityClass(method, RpcCallback.class, 0);
                Object obj = decodeData(rpcBuffer, returnType);
                rpcData.setData(obj);
            } else {
                Class returnType = rpcMapperInfo.getReturnType();
                Object obj = decodeData(rpcBuffer, returnType);
                rpcData.setData(obj);
            }
        }
    }

    private boolean isCallback(RpcMapperInfo rpcMapperInfo) {
        boolean isCallback = Arrays.stream(rpcMapperInfo.getParameterTypes()).filter(pc->pc.isAssignableFrom(RpcCallback.class)).findFirst().isPresent();
        return isCallback;
    }

    private Object decodeData(RpcBuffer rpcBuffer, Class clazz) {
        if (clazz == Void.class || clazz == void.class) {
            return null;
        } else {
            Object obj = objectCodec.decode(rpcBuffer, clazz);
            return obj;
        }
    }
}
